﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;

namespace ComputerArchitecture.Numbers
{
    public static class FloatPointNumberExtension
    {
        #region Consts

        public const int FloatExponentSize = 8;
        public const int FloatMantissaSize = 23;
        public const int DoubleExponentSize = 11;
        public const int DoubleMantissaSize = 52;

        #endregion

        #region Methods

        public static UInt32 FloatToInt32Bits(float value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            if (!BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            return BitConverter.ToUInt32(bytes, 0);
        }

        public static UInt64 DoubleToInt64Bits(double value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            if (!BitConverter.IsLittleEndian)
            {
                Array.Reverse(bytes);
            }
            return BitConverter.ToUInt64(bytes, 0);
        }

        public static FloatPointNumber ToFloatPointNumber(this float value)
        {
            uint integerRepresentation = FloatToInt32Bits(value);
            // sign bit
            uint mask = 0x80000000;
            FloatPointNumber floatPointNumber =
                new FloatPointNumber((mask & integerRepresentation) == 0 ? Bit.Zero : Bit.One, FloatExponentSize, FloatMantissaSize);
            mask >>= 1;
            for (int i = 0; i < floatPointNumber.Exponent.Size; ++i)
            {
                if ((mask & integerRepresentation) != 0x0)
                {
                    floatPointNumber.Exponent[i].Set();
                }
                mask >>= 1;
            }
            for (int i = 0; i < floatPointNumber.Mantissa.Size; ++i)
            {
                if ((mask & integerRepresentation) != 0x0)
                {
                    floatPointNumber.Mantissa[i].Set();
                }
                mask >>= 1;
            }
            return floatPointNumber;
        }

        public static float ToFloat(this IFloatPointNumber floatPointNumber)
        {
            ArgumentValidator.NullValidate(floatPointNumber);
            if (floatPointNumber.Size != FloatExponentSize + FloatMantissaSize + 1)
            {
                throw new ArgumentException("Invalid float size");
            }
            uint number = 0x0, mask = 0x80000000;
            foreach (Bit bit in floatPointNumber)
            {
                if (bit.IsSet)
                {
                    number |= mask;
                }
                mask >>= 1;
            }
            byte[] bytes = BitConverter.GetBytes(number);
            return BitConverter.ToSingle(bytes, 0);
        }

        public static double ToDouble(this IFloatPointNumber floatPointNumber)
        {
            ArgumentValidator.NullValidate(floatPointNumber);
            if (floatPointNumber.Size != DoubleExponentSize + DoubleMantissaSize + 1)
            {
                throw new ArgumentException("Invalid float size");
            }
            ulong number = 0x0, mask = 0x8000000000000000;
            foreach (Bit bit in floatPointNumber)
            {
                if (bit.IsSet)
                {
                    number |= mask;
                }
                mask >>= 1;
            }
            byte[] bytes = BitConverter.GetBytes(number);
            return BitConverter.ToDouble(bytes, 0);
        }

        public static FloatPointNumber ToDoublePointNumber(this double value)
        {
            ulong integerRepresentation = DoubleToInt64Bits(value);
            // sign bit
            ulong mask = 0x8000000000000000;
            FloatPointNumber floatPointNumber =
                new FloatPointNumber((mask & integerRepresentation) == 0 ? Bit.Zero : Bit.One, DoubleExponentSize, DoubleMantissaSize);
            mask >>= 1;
            for (int i = 0; i < floatPointNumber.Exponent.Size; ++i)
            {
                if ((mask & integerRepresentation) != 0x0)
                {
                    floatPointNumber.Exponent[i].Set();
                }
                mask >>= 1;
            }
            for (int i = 0; i < floatPointNumber.Mantissa.Size; ++i)
            {
                if ((mask & integerRepresentation) != 0x0)
                {
                    floatPointNumber.Mantissa[i].Set();
                }
                mask >>= 1;
            }
            return floatPointNumber;
        }

        #endregion
    }
}
